其实我对写测试代码很抵触,因为本来写一个项目就很难了,还要写测试代码,最烧脑的就是写测试用例,想着就头疼。

问了一下chatgpt该怎么解决。

最后我来说一句,不要被测试吓到了,写想得到的测试用例即可,因为即使是AWS这样的大厂,也会因为时间不够而测试不够,所以放松点,写就行了,测试用例本身也不是万能的。

现在的AI时代,更不用担心写测试代码了,把代码交给AI,叫它们给出测试代码先打个样,我照着样子写就行了。

🧘‍♂️ 一、先接受一个现实

“不是所有代码都值得被测试。”

👉 不是让你覆盖全部。测试是一个投资,要投入少、回报高 只测:

📊 实际经验法则:

一个成熟的前端项目,测试覆盖率达到 60% 就很优秀了


🪄 二、换个思路:测试 ≠ 写更多代码

可以把测试当成「自动化的手工测试」。

比如,你现在开发时是不是会这样:

  1. 打开浏览器
  2. 点击按钮
  3. 看看列表是不是更新了

那么测试其实就是:

💡 就是把“自己手点”的流程,变成一段能自动跑的脚本。 以后你改完代码,它自己帮你点一遍,比你手动测试强多了。


🧩 三、从小处入手(5分钟入门计划)

你只需要写 第一个最简单的测试,就能缓解90%的“抗拒感”。

👇 示例:

✅ 不需要 mock ✅ 不需要配置 CI ✅ 只需要看到测试通过的 ✅ 绿色标志,你就会开始“有点爽” 😄


⚙️ 四、逐渐提升节奏(循序渐进)

阶段目标行动
🥉 第1阶段学会写最简单的组件测试测按钮、输入框
🥈 第2阶段学会 mock API用 MSW 模拟接口
🥇 第3阶段学会测试 React Query / Mutation验证数据流更新
🏆 第4阶段集成到 CI每次提交自动测试

🔧 五、写测试的小技巧

技巧说明
✅ 用“行为”命名测试比如:it("should add hero when button clicked")
🔁 不写长测试每个测试只验证一件事
🧠 模拟真实用户使用 fireEvent.change()fireEvent.click()
⏱ 跑测试快Vitest 比 Jest 快好几倍
🛠 遇到难测的逻辑?提取成纯函数再单测就行

💬 六、再来点鸡汤(但是真实的)

“没有测试的代码,就像一栋没人检查的房子:看着挺好,其实地基可能歪了。”

很多团队直到出 bug 才后悔:

测试其实是:

花一点点时间,现在安心; 不写测试,将来加班调 bug。


❤️ 七、最实用的策略总结

目标做法
想轻松上手从最简单的组件测试写起
想测试 API用 MSW 模拟接口
想避免回归 bug测核心逻辑(useQuery、mutation)
没动力跑一次自动测试,看一堆 ✅ 绿点就上头了 😆

测试代码需要些很多测试用例吗?

🧩 一、先说结论:不是测试越多越好,而是要写“有价值的测试”

✅ 写 3 个关键测试比写 30 个无用测试更重要。

测试的数量 ≠ 质量。 关键是要“覆盖可能出 bug 的分支”。


🎯 二、测试用例的目标:验证行为,而不是代码行

很多初学者会这样想:

“每个函数我都写几个测试,这样安全。”

其实不对。 真正有价值的测试,是验证预期行为是否成立

举个例子 👇

很多人会写:

这样就够了 ✅ 不需要再测负数、NaN、undefined —— 因为在业务上不会发生这些输入。

测试的目标是“确保真实用户不会踩坑”, 不是“证明代码对所有可能输入都无敌”。


⚙️ 三、那到底要写多少测试用例?(经验法则)

业务复杂度建议的测试数量举例
简单函数1~2 个加法、纯展示逻辑
有分支逻辑的函数每个分支 1 个登录成功 / 失败
表单3~4 个输入验证、提交、错误信息
API 请求2~3 个成功响应 / 失败响应
UI 交互(点击/切换)2~3 个点击按钮 / 路由跳转
状态管理逻辑(React Query / Zustand)3~5 个数据缓存、loading 状态、错误状态

👉 重点是分支覆盖,不是输入覆盖。


🧠 四、用「Given / When / Then」三步法写用例(非常实用)

写测试时不要想“要测几种情况”, 而是想:

  1. Given:给定一个初始状态
  2. When:当用户或系统做了某个动作
  3. Then:预期结果是什么

比如👇

✔️ 一条用例,就代表一次完整的“用户行为验证”。


🧩 五、一个通用策略:每个功能写 2~3 个测试即可

例如「添加英雄」功能:

测试目标用例示例
✅ 成功添加输入正确信息 → 成功提示
⚠️ 验证失败不填内容 → 显示错误
❌ 服务器错误返回500 → 显示“添加失败”

这样写完后,这个功能几乎就“安全了”。 不需要 10 个输入组合的测试。


🧠 六、记住这句黄金法则

💬 “你不是在证明代码没错,而是在确保用户不会出问题。”

所以:


📈 七、实践经验(真实团队中的标准)

团队类型测试策略
初创团队只测关键功能(10–20 条用例)
中型团队每个业务模块 3–5 条核心用例
大型产品(有 CI)关键逻辑全测(50–200 条用例)

即使是成熟的产品,也不会追求“全面覆盖”。 而是保证:

“核心功能永远不会因为重构崩掉。”


✅ 总结:测试数量黄金法则

层级每个模块推荐用例数量备注
逻辑函数2~3 个分支覆盖
表单3~4 个成功 / 校验 / 错误
API 逻辑2~3 个成功 / 失败
页面3~5 个行为路径
整体项目30~50 个中型项目就足够

 

1 - Introduction

测试非常重要

image-20251109145540027

我们通常依赖手动测试,手动测试有很多缺点。

image-20251109145718106

自动测试有下面的好处:

image-20251109145844350

课程结构:

image-20251110170317902

2 - Jest vs React Testing Library

同时需要Jest和RTL来写测试代码。

image-20251110170532037

image-20251110170632185

3 - Types of Tests

image-20251110170721785

单元测试

image-20251110170806228

集成测试

image-20251110170832268

端到端测试

image-20251110170942191

我们到底应该写哪种类型的测试?都要写,但是就像下图显示的那样,unit tests写的最多,integration tests数量居中,而E2E测试写的数量最少。

image-20251110171031979

React Testing Library (RTL) 的核心设计理念是“测试组件的行为而不是实现细节”,其目标是让测试更接近用户如何实际使用应用程序的方式,从而提供更可靠的测试信心。

image-20251110171300603

小结:

image-20251110171444894

4 - What is a Test?

自动测试是什么?

是一段代码,当测试对象的真实输出与预期输出不一致时,会抛出错误。

下面是模拟测试代码的片段,测试流程就是这样的,当真实输出和预期输出不一致,就会报出有意义的错误。

image-20251110172007019

5 - Project Setup

1、创建项目:npm create vite@latest react-testing,添加typescript。

因为vite创建项目默认没有安装jest和RTL,所以需要自己安装。可以使用下面的代码来查看是否安装了。

如果显示-- (empty),说明没有安装。

因为这个课程是2022年的,到现在create-react-app命令已经不推荐了,jest也因为执行慢而不推荐了,所以我决定安装vitest和RTL,如果遇到jest的代码,我就搜索看怎么使用vitest来写,就这样了。

安装依赖:npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom

为什么还要安装jest-dom呢?

@testing-library/jest-dom 虽然名字里有 “jest”, 但它不是 Jest 专属的库,而是一个“断言扩展库”。

它的作用是: 👉 给测试框架(Jest、Vitest、Testing Library 等) 扩展出更多更自然的人类可读断言(matchers)。Vitest 直接兼容 Jest 的断言扩展。

为什么要安装jsdom?

💡 jsdom 是一个用 纯 JavaScript 实现的“浏览器环境模拟器”

在 Node.js 中,没有浏览器、没有 window、没有 document 但 React 组件的渲染、事件监听、DOM 操作都依赖这些对象。 👉 所以我们需要 jsdom在 Node 环境下伪造一个浏览器环境

2、修改 vite.config.ts

打开 vite.config.ts,在配置里加上 test 字段 👇

3、在package.json里面添加测试脚本命令

4、创建src/setupTests.ts

这个文件用来引入jest-dom的扩展断言:

这个文件的详细作用:

🧩 一、setupTests.ts 的作用是什么?

setupTests.ts(或 setupTests.js)是一个 测试运行前的全局初始化文件

简单理解:

每次执行 npm run test,Vitest 会在运行任何测试文件之前先执行这个文件。

它的作用包括:


✅ 举个例子:

src/setupTests.ts

这样你在测试文件中就可以直接写:

否则 TypeScript 会报错:

Property 'toBeInTheDocument' does not exist on type 'Assertion'.


⚙️ 二、这个文件“用在哪里”?

Vitest 在你的 vite.config.ts 里识别它。 我们在配置文件中这样写👇:

这表示:

在每个测试文件运行前,先执行一次 setupTests.ts 文件的内容。

这就像 Jest 里常用的 setupFilesAfterEnv 一样的概念。


🚀 三、npm run test 到底运行哪个文件?

1️⃣ 执行命令

当你运行:

其实执行的是 vitest 命令。

这通常在 package.json 里配置的,比如:


2️⃣ Vitest 会自动:

  1. 读取 vite.config.tstest 配置。
  2. 根据 include 规则(默认为 **/*.{test,spec}.{js,ts,jsx,tsx})加载所有测试文件。
  3. 在执行每个测试文件之前,先运行一次 setupTests.ts
  4. 按顺序执行所有测试。

📦 四、执行流程总结图


🧠 五、总结对照表

文件/命令作用是否自动执行
setupTests.ts在所有测试运行前执行的初始化逻辑✅ 是
vite.config.ts测试环境配置(jsdom、globals等)✅ 是
npm run test启动测试执行流程✅ 是
*.test.tsx实际编写的测试用例文件✅ 是

5、声明vitest相关的types

给 TypeScript 告诉「我在用 Vitest」。

如果tsconfig.json文件的references是引用的其它文件,比如说tsconfig.app.json,那么就需要在这个文件里面改:

image-20251110205544971

6、创建测试文件

比如说我创建App.test.tsx这个测试文件,简单测试一下:

三个函数的意义:

describe:像章节标题,用来“分组”一组相关的测试;

it(或 test:一个具体的测试用例(Test Case);

expect:测试的核心断言,用来“验证”结果是否正确。

6 - Running Tests

上节课已经编写了App.tsx的测试文件,可以使用npm run test来看一下测试效果。

报错:Error: [vitest-pool]: Timeout starting forks runner.

解决办法:

在 Windows 上运行时,有时防病毒软件(尤其是 360、Defender、McAfee) 会阻止 Node 创建子进程,Vitest 的多进程初始化就会超时。

✅ 解决方法:

将package.json里面的命令改为:test: vitest --pool=threads。改用线程池模式。

重新测试,可以看到一个测试文件,两个测试用例测试通过了:

image-20251110211806936

7 - Anatomy of a Test

anatomy:解剖。

这节课来学习test文件的结构,解剖一下。按照老师的测试文件来讲解,test与it类似。

image-20251110212132495

test函数的作用:定义一个独立的测试用例。

image-20251110212523720

参数类型描述
namestring测试用例的描述名称。
fnfunction实际包含测试逻辑的回调函数。
timeoutnumber可选参数该测试用例的最大执行时间(毫秒)

8 - Your First Test

创建一个很简单的组件:

编写测试文件:

运行测试,通过:

image-20251110214047768

9 - Test Driven Development

TDD的标准是下面这样:

image-20251110214301652

但是TDD想要真的实现起来,困难重重,原因是初期学习成本高、时间压力、遗留系统问题等等,所以现实使用的时候,需要妥协:

这节课只是介绍一下按照TDD来写的流程,了解即可。

1、根据需求,编写测试用例

需求:Greet should render the text hello and if a name is passed into the component, it should render hello followed by the name.

这时候就可以把测试运行起来了npm run test,肯定是会看到报错的,然后就根据报错把组件代码编写完成。

2、编写组件代码,直到测试通过

image-20251111104345651

第三步就不展示了,因为案例代码很简单。总之,这节课就是演示一下TDD的开发流程,不一定要按这个流程来做。

10 - Jest Watch Mode

这节课学习了解jest的watch模式,watch模式是jest命令的一个选项,默认选中的。jest会监听上一次commit后变更过的文件,只执行这些变更过的文件。

image-20251111104629973

由于我使用的是vitest,我就以vitest来说明:

默认情况下,Vitest 会以 “watch 模式(监视模式)” 启动,除非你明确加上 run 参数。

👉 默认进入 **watch 模式

👉 进入 一次性运行模式(不 watch)

概念

Watch 模式就是:Vitest 会 持续监听你的文件变化(包括源码文件 .ts, .tsx、测试文件 .test.ts、配置文件等),只要检测到文件变动,它会 自动重新运行相关的测试

好处

优点说明
⚡ 快速反馈你修改代码后不需要手动再跑 npm run test,Vitest 会自动检测到变化并重新运行测试。
🎯 局部测试Vitest 会智能判断哪些测试受影响,只重新运行相关的测试文件,而不是所有测试,提高速度。
🔁 提高开发效率可以边写代码边看测试结果实时刷新,类似于开发服务器的 “热重载(HMR)” 体验。
🧩 集成 IDE在 VSCode 等编辑器里运行 watch 模式时,可以结合断点调试、错误提示、快照测试等工具使用。

11 - Filtering Tests

运行测试之后,按h键可以显示help菜单,里面有一些命令:

image-20251111110258763

1、需求:当前过滤只测试一个文件。

可以按p键,然后输入文件名称,使用方向键来选择文件,选中之后按enter即可。

这个就是在watch模式下,能够专注于测试某个文件(修改其它的文件,不会触发watch监听测试),性能会更加好。如果想还原,可以输入a键,就会测试所有文件。

2、需求:当前过滤只测试某个测试用例。

可以按t键,输入起的测试名称,就是it或者test的第一个参数,不需要完整输入,因为搜索是模糊搜索,就会出现一些选项,使用方向键来选择,使用enter键来选中。

这个就是在watch模式下,能够专注于测试某个测试用例(修改其它的测试用例,不会触发watch监听测试),性能会更加好。如果想还原,可以输入a键,就会测试所有文件。

3、可以使用test.only()或者it.only()来指定一个测试文件里面测试哪些用例。比如说这样:

image-20251111111729850

测试之后会显示有一个测试用例被跳过了。

image-20251111111812046

4、可以使用test.skip()或者it.skip()来指定一个测试文件跳过哪些测试用例。比如说:

image-20251111112046370

测试之后就会显示有一个测试用例被跳过了。

image-20251111112037012

only和skip方法,可以结合上面的过滤一起使用,性能会更好。

12 - Grouping Tests

如果你想让测试用例组织成组,可以使用describe函数,它用于组织测试结构分组测试用例,让测试代码更清晰、可维护。

第一个参数是分组的名称;第二个参数是一个函数,包含测试用例。

image-20251111112912676

Greet.test.tsx文件里面的测试用例,都放到describe里面去,分组名称是Greet。

image-20251111113334969

可以看到,制定了分组名称之后,测试结果看上去就很有层次了。

image-20251111113321438

1、describe可以使用only和skip方法,来只测试或者跳过测试。

2、describe可以嵌套使用

image-20251111113830832

测试结果:

image-20251111113846414

3、一个测试文件里面可以有多个describe

image-20251111113903542

测试结果:

image-20251111113915646

13 - Filename Conventions

jest认为合法的测试文件名称,约定如下:

image-20251111114153862

推荐方法是将测试文件靠近你需要测试的代码文件,这样相对引入路径会短一些。

那么vitest里面的约定是什么呢?

Vitest 默认会扫描以下几种命名模式的文件,并将它们识别为测试文件

也就是说,只要文件名符合:

当然vitest允许你在配置文件(通常是 vitest.config.ts)里自定义测试文件的匹配规则:

你可以自由调整,比如:

推荐的方式就是将测试文件和源文件放在一起,这样配置文件也不用改了:

14 - Code Coverage

metric:度量、指标。

代码覆盖度,包含下面四个指标。

image-20251111115326399

运行:npx vitest --coverage

image-20251111130231353

1、指定目录或文件

npx vitest run --coverage src/components

image-20251111131018329

后面可以使用空格隔开,添加多个目录或文件地址。

2、报告的类型

reporter 类型(--coverage.reporter

Reporter输出位置说明
text控制台详细文本报告
text-summary控制台简略总结
htmlcoverage/index.html图形化网页报告
jsoncoverage/coverage-final.json原始数据,供分析
json-summarycoverage/summary.json汇总数据
lcovcoverage/lcov.info常用于 CI 工具,如 Codecov / Coveralls

默认是输出text 和 html,text输出在terminal控制台里面,html是命令执行之后,会在项目根目录插件一个coverage的文件夹,里面存放的html文件。

3、配置

可以在vite.config.ts里面配置coverage属性,比如说coverage.exclude属性,可以排除一些类型的文件。

这些配置在CI/CD时很有效,可以实时查看覆盖率。

15 - Assertions

断言(Assertion) 指的是:在测试中,用来“判断结果是否符合预期”的语句。

image-20251111133127220

✅ 举个例子

这里:

expect

vitest中通常使用expect来创建一个期望对象,与一个匹配器matcher函数一起使用。

image-20251111133543667

断言的基本结构:

matchers

vitest自身提供的matchers可以在这里找到:https://vitest.dev/api/expect.html。由于vitest兼容jest,所以jest的matchers也可以在vitest里面使用,在这里可以找到:https://jestjs.io/docs/using-matchers

但上面这些都是针对JS的测试,我们在react项目里面还要针对UI和DOM进行测试。这时候使用的是jest-dom库提供的matchers。在这里可以找到:https://github.com/testing-library/jest-dom

16 - What to test?

在react项目中,到底要测试什么呢?

测试组件渲染;如果有props,那么要加上props一起测试渲染;测试组件的不同状态; 测试组件如何响应交互事件。

image-20251111135223677

不要测什么?

实现细节不要测;第三方包的代码不要测(但是如果组件使用了第三方的代码,那么需要测试自己的组件);从用户角度看不重要的代码不要测。image-20251111135421501

17 - RTL Queries

编写一个测试用例的过程就是下面的步骤:

1、渲染组件,使用RTL提供的render方法

2、找到组件中的一个被渲染的元素

3、断言被找到的元素,看是否通过测试

image-20251111140421588

那怎么找到元素呢?这时候就要使用RTL queries相关的方法了。分为查找单个元素和查找多个元素。

image-20251111140808464

方法后面的..表示查找的各种依据,比如说getByRolegetByText等等,老师只是总结了一些前缀,完整的方法就是像getByText这样。

RTL中的查询方法和DOM testing library的方法使用方法大部分一致,只是RTL中的查询方法,不需要写第一个参数,因为第一个参数已经绑定到document对象了。

image-20251111141522997

RTL相关的查询方法,可以从https://testing-library.com/docs/react-testing-library/cheatsheet找到,使用方法可以直接看testing-library里面的core API。https://testing-library.com/docs/queries/byrole

 

下面的几节课要开始讲解getBy开头的查询方法。

image-20251111141218162

18 - getByRole

getByRole方法根据给定的role来查询元素。role指的是ARIA role,用于告诉屏幕阅读器、盲文显示器等辅助技术,某个元素在页面上扮演的功能角色是什么。

image-20251111142111539

很多HTML语意元素默认有role属性,比如说button的role属性值就是button。在这里可以查询HTML元素默认的role属性值https://www.w3.org/TR/html-aria/#docconformance。或者问AI即可。

如果你使用的元素没有默认role属性,或者你想自定义role属性,可以使用role=xxx来指定。

image-20251111142213549

创建application.tsx文件,在App.tsx里面引入它,删除App.test.tsx文件,因为这个课程之后的事情都与它无关了。

不要担心这个form组件的测试文件该怎么写?老师这节课主要是讲解使用getByRole方法,来查询主要的交互元素是否渲染了。

测试看一下:

image-20251111145040530

19 - getByRole Options

这节课来学习getByRole的第二个参数里面的一些配置参数,第二个参数是一个对象,里面有很多参数。全部参数如下:

image-20251111145225927

1、name参数

在Application组件里面,添加一个textarea组件。

image-20251111145337164

可以看到测试报错:

image-20251111145436004

提示信息很清楚,就是找到了多个符合role="textbox"的元素。

可以添加name属性来解决。name属性指的是什么呢?

1、对于表单元素(如 <input><textarea><select>),它们的 name 通常由关联的 <label> 元素提供。

2、对于大多数具有文本内容的元素,name 就是它的可见文本。

HTML 元素预期 name 属性的值
<button>Submit</button>"Submit"
<a href="...">Home Page</a>"Home Page"
<h1>Welcome!</h1>"Welcome!"

3、如果元素没有可见文本或标签,但开发者使用了 ARIA 属性来提供名称,那么 name 属性就是这些 ARIA 属性的值。

image-20251111145648991

所以,测试代码可以这样改:

image-20251111150058821

测试OK。

2、level参数

level参数对于role=heading的元素有用,因为h1~h6标签的role都是heading。

需求:测试h1和h2元素都渲染成功。

image-20251111150848699

测试OK。

老师说getByRole应该是查询的第一选择,如果它不管用,再考虑其它的。

20 - getByLabelText

作用:通过表单元素的可见标签文本来查找该表单元素,用于获取与特定标签(Label)关联的表单控件。

比如说有下面的HTML结构:

那么就可以根据label标签文本Email Address,来找到与之相关的input输入框。

相关是通过label元素的for属性(react中就是htmlFor属性),指定为input元素的id属性的值。

image-20251111151213380

1、基本使用

image-20251111151841580

测试OK。

2、即使是wrapper的形式,也可以使用

这种情况称为“包裹式关联”,label上不需要添加for属性。

比如说Application.tsx里面有这段代码:

还是可以通过label里面的文本来查询到相应的input。

image-20251111152404135

测试OK。

3、optional参数

image-20251111152518042

假设多个labe里面的文本内容相同,此时可以指定selector参数为具体的元素名称,来查找。

image-20251111152636347

21 - getByPlaceholderText

这个方法根据元素的placeholder属性的值来查找匹配,这个很好理解、很简单。

image-20251111152806596

Application组件里面有一个指定了placeholder属性的元素:

image-20251111153008412

使用getByPlaceholderText来查找:

image-20251111153148643

测试OK。

22 - getByText

作用:通过元素的文本内容(Text Content)来查找 DOM 节点。可以用它来查找几乎所有非表单元素中的文本内容,但是最好只用来查找p、div、span这些元素,因为查找方法是有优先级的,getByRole的优先级最高。

image-20251111153249892

需求:查找这个p元素。

image-20251111154326919

查找代码,测试OK:

image-20251111154419891

23 - getByDisplayValue

这个方法查找匹配展示值的元素,通常是input、textarea、select。

image-20251111154517015

需求:为input设置value属性,根据value查找这个input。

image-20251111154845550

测试OK。

image-20251111154926765

24 - getByAltText

image-20251111155035305

需求:根据alt属性值,查询元素。

代码:

image-20251111155331050

测试OK。

25 - getByTitle

image-20251111155419341

需求:根据title属性值,查询元素。

代码:

image-20251111155709100

测试OK。

26 - getByTestId

如果上面的几个方法都不行,那么就使用这种方法。添加data-testid属性即可。

image-20251111155752048

需求:根据testid来查询元素。

代码:

image-20251111160114535

测试OK。

27 - Priority Order for Queries

上面学习的8种方法是有优先级的:

优先级查询方法场景和目的示例
1. 语义和角色getByRole查找可交互元素(按钮、链接、输入框、图片、列表等)。这是 RTL 最推荐的方法,因为它基于 WAI-ARIA 规范。screen.getByRole('button', { name: /save/i })
2. 标签getByLabelText查找与可见标签关联的表单元素(输入框、选择框等)。强制保证了表单的可访问性。screen.getByLabelText(/password/i)
3. 占位符getByPlaceholderText查找带有特定 placeholder 的输入框。但请注意,placeholder 不应用于可访问性名称,应作为辅助查询。screen.getByPlaceholderText('Enter email')
4. 文本内容getByText查找任何元素的纯文本内容。最适合查找静态文本、标题、段落等非交互元素。screen.getByText('Welcome to the app')
5. 显示值getByDisplayValue查找当前显示特定值的表单元素(例如,已填写的输入框或选择框)。screen.getByDisplayValue('John Doe')
6. 替代文本getByAltText查找图片(<img>)或自定义控件(如 SVG 图标)的 alt 文本。screen.getByAltText('Company Logo')
7. 标题getByTitle查找带有 title 属性的元素。通常用于提供额外提示信息。screen.getByTitle('Close Window')
8. 测试 IDgetByTestId查找带有 data-testid 属性的元素。这是最后的选择,仅用于那些没有其他语义或文本标识的元素。screen.getByTestId('loading-spinner')

 

image-20251111160535948